package net.protocol.diameter.dictionary;

import java.util.*;

/**
 * @author jinhongw@gmail.com
 */
public abstract class DiameterFormat implements Iterable<AVPRule> {
    private final int code;
    private final String name;

    protected final Collection<AVPRule> rules = new ArrayList<AVPRule>();

    // Map<VendorId, Map<AVP-Code, AVPFormat>>
    private final Map<Integer, Map<Integer, AVPRule>>
            map = new HashMap<Integer, Map<Integer, AVPRule>>();

    /**
     * @param code [Command-Code | AVP-Code]
     * @param name [Command-Name | AVP-Name]
     */
    protected DiameterFormat(int code, String name) {
        this.code = code;
        this.name = name;
    }

	/**
	 * Appends the specified element[<code>AVPRule</code>] to this <code>DiameterFormat</code>.
	 * 
	 * @param rule
	 *            element to be appended to this <code>DiameterFormat</code>
	 * 
	 * @throws UnsupportedOperationException
	 *             If isMessage()== false && isGroup() == false
	 */
    public void add(final AVPRule rule) {
        if (isMessage() == false && isGroup() == false)
            throw new UnsupportedOperationException();

        rules.add(rule);

        AVPFormat af = rule.getAVPFormat();
        int vendorId = af.getVendorId();
        int code = af.getCode();

        if (map.get(vendorId) == null)
            map.put(vendorId, new HashMap<Integer, AVPRule>());

        map.get(vendorId).put(code, rule);
    }

    /**
     *
     *
     * @param vendorId AVP Vendor Id
     * @param code AVP Code
     * @return Diameter AVP Rule
     */
    public AVPRule get(int vendorId, int code) {
        Map<Integer, AVPRule> cavps = map.get(vendorId);
        if (cavps == null)  {
            // String msg = "Can't find a AVP rule with the given vendor id(" + vendorId + ")";
            // throw new NullPointerException(msg);
            return null;
        }
        return cavps.get(code);
    }

	/**
	 * Returns <tt>true</tt> indicating this <code>DiameterFormat</code> is
	 * <code>MessageFormat</code>. Returns <tt>false</tt> indicating this
	 * <code>DiameterFormat</code> is <code>AvpFormat</code>.
	 * 
	 * @return a <code>boolean</code> indicating whether or not <code>MessageFormat</code>
	 */
    public abstract boolean isMessage();

	/**
	 * Returns <tt>true</tt> indicating this <code>DiameterFormat</code> is
	 * Group <code>AvpFormat</code>. Returns <tt>false</tt> indicating this
	 * <code>DiameterFormat</code> is Non-Group <code>AvpFormat</code>.
	 * 
	 * @return a <code>boolean</code> indicating whether or not Group <code>AvpFormat</code>
	 */
    public abstract boolean isGroup();

	/**
	 * Returns an iterator over the <code>AVPRule</code>s in this <code>DiameterFormat</code>.
	 * 
	 * @return an <tt>Iterator</tt> over the AVPRules in this <code>DiameterFormat</code>
	 */
    @Override
    public Iterator<AVPRule> iterator() {
        return rules.iterator();
    }

	/**
	 * Returns the number of <code>AVPRule</code> in this <code>DiameterFormat</code>.
	 * 
	 * @return the number of <code>AVPRule</code> in this <code>DiameterFormat</code>
	 */
    public int size() {
        return (rules == null) ? 0 : rules.size();
    }

	/**
	 * Returns <tt>true</tt> if this <code>DiameterFormat</code> contains no
	 * AVPRules.
	 * 
	 * @return <tt>true</tt> if this <code>DiameterFormat</code> contains no <code>AVPRule</code>.
	 */
    public boolean isEmpty() {
        return (rules == null) || (rules.size() == 0);
    }

    /**
     * Returns this DiameterFormat's code[Command-Code | AVP-Code].
     *
     * @return Code[Command-Code | AVP-Code]
     */
    public int getCode() {
        return code;
    }

    /**
     * Returns this DiameterFormat's name[Command-Name | AVP-Name].
     *
     * @return Name[Command-Name | AVP-Name]
     */
    public String getName() {
        return name;
    }

    @Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + code;
		result = prime * result + ((map == null) ? 0 : map.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		DiameterFormat other = (DiameterFormat) obj;
		if (code != other.code)
			return false;
		if (map == null) {
			if (other.map != null)
				return false;
		} else if (!map.equals(other.map))
			return false;
		return true;
	}
}
